package registry

import (
	"context"
	"fmt"
	"log/slog"
	"strings"

	clientv3 "go.etcd.io/etcd/client/v3"
	"google.golang.org/grpc/resolver"
)

// ResolverBuilder 实现了 gRPC 的 resolver.Builder 接口
type ResolverBuilder struct {
	client *clientv3.Client // ETCD 客户端
}

// NewResolverBuilder 创建一个新的 ResolverBuilder 实例
func NewResolverBuilder(client *clientv3.Client) resolver.Builder {
	slog.Info("创建新的 ResolverBuilder 实例")
	return &ResolverBuilder{client: client}
}

// Build 创建一个新的 Resolver 实例
func (b *ResolverBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
	slog.Info("创建新的 Resolver 实例", slog.String("scheme", target.URL.Scheme), slog.String("endpoint", target.Endpoint()), slog.String("authority", target.URL.Host))
	ctx, cancel := context.WithCancel(context.Background())
	r := &Resolver{
		client: b.client,
		target: target,
		cc:     cc,
		ctx:    ctx,
		cancel: cancel,
	}
	if err := r.start(); err != nil {
		return nil, err
	}
	return r, nil
}

// Scheme 返回 etcd 的 scheme
func (b *ResolverBuilder) Scheme() string {
	return ServiceDNS
}

// Resolver 实现了 gRPC 的 resolver.Resolver 接口
type Resolver struct {
	client *clientv3.Client    // ETCD 客户端
	target resolver.Target     // 目标服务，包含要解析的服务信息
	cc     resolver.ClientConn // gRPC 客户端连接
	ctx    context.Context     // 上下文，用于控制生命周期
	cancel context.CancelFunc  // 取消函数，用于取消上下文
	addrs  []resolver.Address  // 地址列表，存储解析后的服务地址
}

// start 开始监视 etcd 中的服务变化
func (r *Resolver) start() error {
	slog.Info("开始监视服务变化", slog.String("target", r.target.String()))
	// 初始化时, 要先同步一次
	if err := r.sync(); err != nil {
		slog.Error("初始同步失败", slog.String("error", err.Error()))
		return err
	}
	go r.watch()
	return nil
}

// watch 监视 etcd 中的服务变化
func (r *Resolver) watch() {
	keyPrefix := fmt.Sprintf("/%s/%s/", ProjectName, r.target.Endpoint())
	slog.Info("监视键前缀", slog.String("key prefix", keyPrefix))
	watchChan := r.client.Watch(r.ctx, keyPrefix, clientv3.WithPrefix())
	for watchResp := range watchChan {
		slog.Info("处理监视事件", slog.Any("events", watchResp.Events))
		var updated bool
		for _, event := range watchResp.Events {
			addr := r.extractAddressFromKey(event.Kv.Key)
			if addr == "" {
				continue
			}
			switch event.Type {
			case clientv3.EventTypePut:
				if !r.exist(r.addrs, addr) {
					r.addrs = append(r.addrs, resolver.Address{Addr: addr})
					updated = true
				}
			case clientv3.EventTypeDelete:
				r.addrs = r.remove(r.addrs, addr)
				updated = true
			}
		}
		if updated {
			_ = r.cc.UpdateState(resolver.State{Addresses: r.addrs})
			slog.Info("客户端连接已更新", slog.Any("addresses", r.addrs))
		}
	}
}

// extractAddressFromKey 从 etcd 键中提取 IP:Port
func (r *Resolver) extractAddressFromKey(key []byte) string {
	parts := strings.Split(string(key), "/")
	if len(parts) > 0 {
		return parts[len(parts)-1]
	}
	return ""
}

// exist 判断这个服务地址是否已经存在, 防止服务访问冲突
func (r *Resolver) exist(addrs []resolver.Address, addr string) bool {
	for _, a := range addrs {
		if a.Addr == addr {
			return true
		}
	}
	return false
}

// remove 移除服务
func (r *Resolver) remove(addrs []resolver.Address, addr string) []resolver.Address {
	for i, a := range addrs {
		if a.Addr == addr {
			return append(addrs[:i], addrs[i+1:]...)
		}
	}
	return addrs
}

// sync 同步获取所有地址信息
func (r *Resolver) sync() error {
	// 获取ETCD中的服务信息
	keyPrefix := fmt.Sprintf("/%s/%s/", ProjectName, r.target.Endpoint())
	slog.Info("同步键前缀", slog.String("key prefix", keyPrefix))
	resp, err := r.client.Get(r.ctx, keyPrefix, clientv3.WithPrefix())
	if err != nil {
		return err
	}

	var newAddrs []resolver.Address
	for _, kv := range resp.Kvs {
		addr := r.extractAddressFromKey(kv.Key)
		if addr != "" {
			newAddrs = append(newAddrs, resolver.Address{Addr: addr})
		}
	}

	r.addrs = newAddrs
	slog.Info("地址列表", slog.Any("addresses", r.addrs))

	_ = r.cc.UpdateState(resolver.State{Addresses: r.addrs})
	slog.Info("客户端连接已更新", slog.Any("addresses", r.addrs))

	return nil
}

// ResolveNow 立即解析服务地址
func (r *Resolver) ResolveNow(o resolver.ResolveNowOptions) {
	slog.Info("立即解析服务地址")
}

// Close 关闭 Resolver
func (r *Resolver) Close() {
	r.cancel()
	slog.Info("Resolver 已关闭")
}
